package org.hawk.gwt.ppc.reflect.emul.java.lang.reflect;

import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;

/**
 * Provides utilities for accessing and creating dynamic arrays.
 * 
 * @author alex.bereznevatiy@gmail.com
 */
public class Array {
	private Array() {
	}

	/**
	 * Creates a new array with the specified component type and length.
	 * Invoking this method is equivalent to creating an array as follows:
	 * <blockquote>
	 * 
	 * <pre>
	 * int[] x = { length };
	 * Array.newInstance(componentType, x);
	 * </pre>
	 * 
	 * </blockquote>
	 * 
	 * @param componentType
	 *            the <code>Class</code> object representing the component type
	 *            of the new array
	 * @param length
	 *            the length of the new array
	 * @return the new array
	 * @exception NullPointerException
	 *                if the specified <code>componentType</code> parameter is
	 *                null
	 * @exception IllegalArgumentException
	 *                if componentType is Void.TYPE
	 * @exception NegativeArraySizeException
	 *                if the specified <code>length</code> is negative
	 */
	public static Object newInstance(Class<?> componentType, int length)
			throws NegativeArraySizeException {
		return newArray(componentType, new int[] { length });
	}

	/**
	 * Creates a new array with the specified component type and dimensions. If
	 * <code>componentType</code> represents a non-array class or interface, the
	 * new array has <code>dimensions.length</code> dimensions and&nbsp;
	 * <code>componentType&nbsp;</code> as its component type. If
	 * <code>componentType</code> represents an array class, the number of
	 * dimensions of the new array is equal to the sum of
	 * <code>dimensions.length</code> and the number of dimensions of
	 * <code>componentType</code>. In this case, the component type of the new
	 * array is the component type of <code>componentType</code>.
	 * 
	 * <p>
	 * The number of dimensions of the new array must not exceed the number of
	 * array dimensions supported by the implementation (typically 255).
	 * 
	 * @param componentType
	 *            the <code>Class</code> object representing the component type
	 *            of the new array
	 * @param dimensions
	 *            an array of <code>int</code> types representing the dimensions
	 *            of the new array
	 * @return the new array
	 * @exception NullPointerException
	 *                if the specified <code>componentType</code> argument is
	 *                null
	 * @exception IllegalArgumentException
	 *                if the specified <code>dimensions</code> argument is a
	 *                zero-dimensional array, or if the number of requested
	 *                dimensions exceeds the limit on the number of array
	 *                dimensions supported by the implementation (typically
	 *                255), or if componentType is Void.TYPE.
	 * @exception NegativeArraySizeException
	 *                if any of the components in the specified
	 *                <code>dimensions</code> argument is negative.
	 */
	public static Object newInstance(Class<?> componentType, int[] dimensions)
			throws IllegalArgumentException, NegativeArraySizeException {
		return newArray(componentType, dimensions);
	}

	private static <T> Object newArray(Class<T> componentType, int[] dimensions) {
		if (dimensions.length < 1) {
			throw new IllegalArgumentException("Wrong dimensions size. Cannot create an arrays with less than 1 dimension.");
		}
		if (componentType.isArray()) {
			throw new IllegalArgumentException(
					"Component type cannot be an array");
		}
		
		return ClassDelegate.forClass(componentType).createArray(new Array(), dimensions);
	}

	/**
	 * Returns the length of the specified array object, as an <code>int</code>.
	 * 
	 * @param array
	 *            the array
	 * @return the length of the array
	 * @exception IllegalArgumentException
	 *                if the object argument is not an array
	 */
	public static native int getLength(Object a)
			throws IllegalArgumentException /*-{
		if (a instanceof Array) {
			return a.length;
		}
		throw @java.lang.IllegalArgumentException::new(Ljava/lang/String;)("Object is not an array");
	}-*/;

	/**
	 * Returns the value of the indexed component in the specified array object.
	 * The value is automatically wrapped in an object if it has a primitive
	 * type.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the (possibly wrapped) value of the indexed component in the
	 *         specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 */
	public static native <T> T get(Object a, int i)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException /*-{
		if (a instanceof Array) {
			return a[i];
		}
		throw @java.lang.IllegalArgumentException::new(Ljava/lang/String;)("Object is not an array");
	}-*/;

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as a <code>boolean</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static boolean getBoolean(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Boolean)get(array, index);
	}

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as a <code>byte</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static byte getByte(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Byte)get(array, index);
	}

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as a <code>char</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static char getChar(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Character)get(array, index);
	}

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as a <code>short</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static short getShort(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Short)get(array, index);
	}

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as an <code>int</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static int getInt(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Integer)get(array, index);
	}

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as a <code>long</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static long getLong(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Long)get(array, index);
	}

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as a <code>float</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static float getFloat(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Float)get(array, index);
	}

	/**
	 * Returns the value of the indexed component in the specified array object,
	 * as a <code>double</code>.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index
	 * @return the value of the indexed component in the specified array
	 * @exception NullPointerException
	 *                If the specified object is null
	 * @exception IllegalArgumentException
	 *                If the specified object is not an array, or if the indexed
	 *                element cannot be converted to the return type by an
	 *                identity or widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#get
	 */
	public static double getDouble(Object array, int index)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		return (Double)get(array, index);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified new value. The new value is first automatically unwrapped
	 * if the array has a primitive component type.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param value
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the array component type is primitive and an unwrapping
	 *                conversion fails
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 */
	public static native void set(Object a, int i, Object v)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException /*-{
		if (a instanceof Array) {
			a[i] = v;
			return;
		}
		throw @java.lang.IllegalArgumentException::new(Ljava/lang/String;)("Object is not an array");
	}-*/;

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>boolean</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param z
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setBoolean(Object array, int index, boolean z)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, z);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>byte</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param b
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setByte(Object array, int index, byte b)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, b);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>char</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param c
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setChar(Object array, int index, char c)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, c);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>short</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param s
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setShort(Object array, int index, short s)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, s);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>int</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param i
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setInt(Object array, int index, int i)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, i);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>long</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param l
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setLong(Object array, int index, long l)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, l);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>float</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param f
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setFloat(Object array, int index, float f)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, f);
	}

	/**
	 * Sets the value of the indexed component of the specified array object to
	 * the specified <code>double</code> value.
	 * 
	 * @param array
	 *            the array
	 * @param index
	 *            the index into the array
	 * @param d
	 *            the new value of the indexed component
	 * @exception NullPointerException
	 *                If the specified object argument is null
	 * @exception IllegalArgumentException
	 *                If the specified object argument is not an array, or if
	 *                the specified value cannot be converted to the underlying
	 *                array's component type by an identity or a primitive
	 *                widening conversion
	 * @exception ArrayIndexOutOfBoundsException
	 *                If the specified <code>index</code> argument is negative,
	 *                or if it is greater than or equal to the length of the
	 *                specified array
	 * @see Array#set
	 */
	public static void setDouble(Object array, int index, double d)
			throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
		set(array, index, d);
	}
}
